﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.ComponentModel;

using InformationExtraction.Common;
using InformationExtraction.Utils;
using InformationExtraction.Interface;

namespace InformationExtraction
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length != 1)
            {
                ErrMsg();
                return;
            }

            InformationType type = InformationType.None;

            bool isAll = false;
            bool error = false;
            switch (args[0].Substring(args[0].IndexOf("-") + 1).ToLower())
            {
                case "acro":
                    type = InformationType.Acronym;
                    break;
                case "cdate":
                    type = InformationType.CRDate;
                    break;
                case "date":
                    type = InformationType.Date;
                    break;
                case "ndate":
                    type = InformationType.NotificationDate;
                    break;
                case "loca":
                    type = InformationType.Location;
                    break;
                case "sdate":
                    type = InformationType.SubmissionDate;
                    break;
                case "home":
                    type = InformationType.Homepage;
                    break;
                case "name":
                    type = InformationType.Name;
                    break;
                case "all":
                    isAll = true;
                    break;
                default:
                    ErrMsg();
                    return;
            }

            if (!isAll)
                ExtractAndVerify(type);
            else
            {
                ExtractAndVerify(InformationType.Homepage);
                ExtractAndVerify(InformationType.Acronym);
                ExtractAndVerify(InformationType.Location);
                ExtractAndVerify(InformationType.Date);
                ExtractAndVerify(InformationType.SubmissionDate);
                ExtractAndVerify(InformationType.NotificationDate);
                ExtractAndVerify(InformationType.CRDate);
                ExtractAndVerify(InformationType.Name);
            }

        }

        private static void ErrMsg()
        {
            Console.WriteLine("Invalid Argument!");
            Console.WriteLine("Usage: InformationExtraction <option>");
            Console.WriteLine("OPTIONS:");
            Console.WriteLine("\t-acro\t\tAcronym");
            Console.WriteLine("\t-all\t\tAll Attributes");
            Console.WriteLine("\t-cdate\t\tCamera Ready Date");
            Console.WriteLine("\t-date\t\tDate");
            Console.WriteLine("\t-home\t\tHomepage");
            Console.WriteLine("\t-loca\t\tLocation");
            Console.WriteLine("\t-ndate\t\tNotification Date");
            Console.WriteLine("\t-sdate\t\tSubmission Date");
            Console.WriteLine("\nExample:  InformationExtraction -all");
        }

        private const string SvmClassifyProgram = "svm_classify.exe";
        private const string SvmLearnProgram = "svm_learn.exe";

        private const string NameTrain = "name_train";
        private const string NameTest = "name_test";
        private const string NameModel = "name_model";
        private const string NameOutput = "name_output";

        private const string HomepageTrain = "hp_train";
        private const string HomepageTest = "hp_test";
        private const string HomepageModel = "hp_model";
        private const string HomepageOutput = "hp_output";

        private const string AcronymTrain = "acro_train";
        private const string AcronymTest = "acro_test";
        private const string AcronymModel = "acro_model";
        private const string AcronymOutput = "acro_output";

        private const string LocationTrain = "location_train";
        private const string LocationTest = "location_test";
        private const string LocationModel = "location_model";
        private const string LocationOutput = "location_output";

        private const string DateTrain = "date_train";
        private const string DateTest = "date_test";
        private const string DateModel = "date_model";
        private const string DateOutput = "date_output";

        private const string CameraReadyDateTrain = "crdate_train";
        private const string CameraReadyDateTest = "crdate_test";
        private const string CameraReadyDateModel = "crdate_model";
        private const string CameraReadyDateOutput = "crdate_output";

        private const string NotificationDateTrain = "notificationdate_train";
        private const string NotificationDateTest = "notificationdate_test";
        private const string NotificationDateModel = "notificationdate_model";
        private const string NotificationDateOutput = "notificationdate_output";

        private const string SubmissionDateTrain = "submissiondate_train";
        private const string SubmissionDateTest = "submissiondate_test";
        private const string SubmissionDateModel = "submissiondate_model";
        private const string SubmissionDateOutput = "submissiondate_output";

        // These are the Win32 error code for file not found or access denied.
        private const int ERROR_FILE_NOT_FOUND = 2;
        private const int ERROR_ACCESS_DENIED = 5;

        private static void ExtractAndVerify(InformationType type)
        {
            string trainFeatures = "";
            string model = "";
            string testFeatures = "";
            string output = "";

            switch (type)
            {
                case InformationType.Homepage:
                    trainFeatures = HomepageTrain;
                    model = HomepageModel;
                    testFeatures = HomepageTest;
                    output = HomepageOutput;
                    break;
                case InformationType.Name:
                    trainFeatures = NameTrain;
                    testFeatures = NameTest;
                    model = NameModel;
                    output = NameOutput;
                    break;
                case InformationType.Acronym:
                    trainFeatures = AcronymTrain;
                    testFeatures = AcronymTest;
                    model = AcronymModel;
                    output = AcronymOutput;
                    break;
                case InformationType.Location:
                    trainFeatures = LocationTrain;
                    testFeatures = LocationTest;
                    model = LocationModel;
                    output = LocationOutput;
                    break;
                case InformationType.Date:
                    trainFeatures = DateTrain;
                    testFeatures = DateTest;
                    model = DateModel;
                    output = DateOutput;
                    break;
                case InformationType.CRDate:
                    trainFeatures = CameraReadyDateTrain;
                    testFeatures = CameraReadyDateTest;
                    model = CameraReadyDateModel;
                    output = CameraReadyDateOutput;
                    break;
                case InformationType.SubmissionDate:
                    trainFeatures = SubmissionDateTrain;
                    testFeatures = SubmissionDateTest;
                    model = SubmissionDateModel;
                    output = SubmissionDateOutput;
                    break;
                case InformationType.NotificationDate:
                    trainFeatures = NotificationDateTrain;
                    testFeatures = NotificationDateTest;
                    model = NotificationDateModel;
                    output = NotificationDateOutput;
                    break;
            }

            try
            {
                for (int testSetNo = 1; testSetNo <= 4; testSetNo++)
                {
                    Extract(type, testFeatures + "_" + testSetNo, testSetNo);
                }

                ExecuteSvmLearning(testFeatures, trainFeatures, model, 4);
                ExecuteSvmClassify(testFeatures + "_" + 4, model + "_" + 4, output + "_" + 4);


                for (int testSetNo = 1; testSetNo <= 4; testSetNo++)
                {
                    ExecuteSvmLearning(testFeatures, trainFeatures, model, testSetNo);
                    ExecuteSvmClassify(testFeatures + "_" + testSetNo, model + "_" + testSetNo, output + "_" + testSetNo);
                }
            }
            catch (Win32Exception e)
            {
                if (e.NativeErrorCode == ERROR_FILE_NOT_FOUND)
                {
                    Console.WriteLine(e.Message + ". Check the path.");
                }

                else if (e.NativeErrorCode == ERROR_ACCESS_DENIED)
                {
                    Console.WriteLine(e.Message +
                        ". You do not have permission to print this file.");
                }

                System.Environment.Exit(0);

            }
            catch (IOException e)
            {
                Console.WriteLine("Error: " + e.Message);
                Console.WriteLine("Advice: Please check the data set 1-4, it is possible that the data sets have not copy to the current workspace");
                Console.WriteLine("");
                System.Environment.Exit(0);
            }

            ClassifyReport report = StatisticResult(type, trainFeatures, output);

            Console.WriteLine(report.ToString());

            StreamWriter sw = new StreamWriter(type + "_result.txt");
            sw.Write(report.ToString());
            sw.Close();
            
        }

        private static ClassifyReport StatisticResult(InformationType type, string testFeatures, string output)
        {
            ClassifyReport report = new ClassifyReport();

            for (int i = 1; i <= 4; i++)
            {
                int nTestSize = 0;

                int nPositiveSize = 0;
                int nNegativeSize = 0;

                int nPositiveInResult = 0;
                int nNegativeInResult = 0;

                int nPP = 0;
                int nNN = 0;
                int nPN = 0;
                int nNP = 0;

                StreamReader testReader = new StreamReader(testFeatures + "_" + i.ToString());
                StreamReader resultReader = new StreamReader(output + "_" + i.ToString());
                
                string testLine = testReader.ReadLine();
                string resultLine = resultReader.ReadLine();

                while (!testReader.EndOfStream && !resultReader.EndOfStream)
                {
                    nTestSize++;

                    if (Convert.ToDouble(testLine.Substring(0, testLine.IndexOf(" "))) > 0)
                    {
                        nPositiveSize++;
                        if (Convert.ToDouble( resultLine) > 0)
                        {
                            nPositiveInResult++;
                            nPP++;
                        }
                        else
                        {
                            nNegativeInResult++;
                            nPN++;
                        }
                    }
                    else
                    {
                        nNegativeSize++;
                        if (Convert.ToDouble(resultLine) > 0)
                        {
                            nPositiveInResult++;
                            nNP++;
                        }
                        else
                        {
                            nNegativeInResult++;
                            nNN++;
                        }
                    }

                    testLine = testReader.ReadLine();
                    resultLine = resultReader.ReadLine();
                }

                if (nPositiveInResult > 0 && nPositiveSize > 0)
                {
                    report.Precision[i - 1] = (float)nPP / (float)nPositiveInResult;
                    report.Recall[i - 1] = (float)nPP / (float)nPositiveSize;
                }
                resultReader.Close();
                testReader.Close();
            }

            report.Attribute = type;
            return report;
        }

        

        private static void Extract(InformationType type, string train, int testSetNo)
        {
            // 抽取文件所在目录列表
            // List<string> trainDirs = new List<string>();

            /*
            for (int i = 1; i < 5; i++)
            {
                if (i == testSetNo)
                    continue;

                trainDirs.Add("./" + i.ToString());
            }
             */

            List<string> testDirs = new List<string>();
            testDirs.Add("./" + testSetNo.ToString());


            // 某具体类型（如：Name, Date etc.）的特征抽取接口
            IFeatureExtractable extract = null;
            switch (type)
            {
                case InformationType.Homepage:
                    extract = new HomepageExtracter();
                    break;
                case InformationType.Name:
                    extract = new NameExtracter();
                    break;
                case InformationType.Acronym:
                    extract = new AcronymExtracter();
                    break;
                case InformationType.Location:
                    extract = new LocationExtracter();
                    break;
                case InformationType.Date:
                    extract = new DateExtracter();
                    break;
                case InformationType.NotificationDate:
                    extract = new NotificationDateExtracter();
                    break;
                case InformationType.SubmissionDate:
                    extract = new SubmissionDateExtracter();
                    break;
                case InformationType.CRDate:
                    extract = new CameraReadyDateExtracter();
                    break;
            }            

            // 构建信息提取器，指定抽取接口、输出文件名、带抽取文件目录列表
            //InformationExtractor ie = new InformationExtractor(extract, train);
            //ie.Directory = trainDirs;
            
            // 抽取特征
            //ie.Extract();

            // 测试集特征抽取
            InformationExtractor ie = new InformationExtractor(extract, train);
            ie.Directory = testDirs;
            ie.Extract();
            
        }

        private static void ExecuteSvmLearning(string test, string train, string model, int testSetNo)
        {
            bool firstWrite = false;
            for (int i = 1; i <= 4; i++)
            {
                if (i == testSetNo)
                    continue;

                StreamReader reader = new StreamReader(test + "_" + testSetNo);
                string content = reader.ReadToEnd();
                reader.Close();

                StreamWriter writer = new StreamWriter(train + "_" + testSetNo, firstWrite);
                writer.Write(content);
                writer.Close();

                if (!firstWrite)
                    firstWrite = true;
            }
            
            ExecuteExternalProgram(SvmLearnProgram, train + "_" + testSetNo + " " + model + "_" + testSetNo);
        }

        private static void ExecuteSvmClassify(string test, string model, string output)
        {
            ExecuteExternalProgram(SvmClassifyProgram, test + " " + model + " " + output);
        }

        private static void ExecuteExternalProgram(string exeName, string arguments)
        {
            Process proc = new Process();

            proc.StartInfo.FileName = Environment.CurrentDirectory + "\\" + exeName;
            proc.StartInfo.UseShellExecute = false;
            proc.StartInfo.CreateNoWindow = true;
            proc.StartInfo.RedirectStandardOutput = true;
            proc.StartInfo.Arguments = " " + arguments;
            proc.Start();

            System.IO.StreamReader reader = proc.StandardOutput;
            string line = reader.ReadLine();

            while (!reader.EndOfStream)
                Console.WriteLine(reader.ReadLine());

            reader.Close();

            proc.WaitForExit();
            proc.Close();
        }
    }
}
